import dawdreamer as dawdmr
import numpy as np
from scipy.io import wavfile
import librosa
import sounddevice as sd

from strophie_daw import DAW
from reaper_remote import remote


def load_audio_file(file_path, sr=None, duration=None):
    sig, rate = librosa.load(file_path, duration=duration, mono=False, sr=sr)
    return sig, rate


class Gate:
    def __init__(self, degree, vol, sym='x'):
        self.sym = sym
        self.degree = degree
        self.vol = vol
    
    def __str__(self):
        return '{}/{}/{}'.format(self.sym, self.degree, self.vol)
    
    
class Sound:
    def __init__(self, path='',  name='', sym='x', sample_rate=None):
        self.path = path
        self.name = name
        self.sym = sym
        self.adsr = [0, 0, 0, 0]
        self.sample = None
        self.sample_rate = sample_rate
        self._params = {}
        
        self.set(path)
        
    def set(self, path):
        if any(path.endswith(suffix) for suffix in ('.wav', '.mp3')):
            self.sample, self.sample_rate = load_audio_file(path, self.sample_rate)
        elif path.endswith('.preset'):
            self.sample = None
            with open(path, 'rt', encoding='utf-8') as file:
                self._params = dict([(texts[0], float(texts[1])) for texts in [line.strip().split('=') for line in file.readlines()]])

    def param(self, name, value):
        self._params['name'] = value


class Groove:
    def __init__(self, pattern='', gate_length=1/8, vol=127):
        self.gates = []
        self.duration = []
        self.time = []
        self.sound = {'x': Sound(sym='x')}
        
        for i, char in enumerate(pattern):
            if char == '-':
                self.duration[-1] += gate_length
            elif char in ('_', ' '):
                pass
            else:
                if char not in self.sound:
                    self.sound[char] = Sound(char)
                self.gates.append(Gate(0, vol, sym=char))
                self.duration.append(gate_length)
                self.time.append(i * gate_length)
                
    def __add__(self, other):
        new_gr = Groove()
        new_gr.gates = self.gates + other.gates
        new_gr.duration = self.duration + other.duration
        new_gr.time = self.time + [t + self.time[-1] + self.duration[-1] for t in other.time]
        new_gr.sound = self.sound.copy()
        new_gr.sound.update(other.sound)
        return new_gr
    
    def __mul__(self, n):
        new_gr = Groove()
        new_gr.gates = self.gates * n
        time = self.time.copy()
        for i in range(n):
            last_time = time[-1]
            # print(time, self.duration, last_time, self.duration[-1])
            time = [t + last_time + self.duration[-1] for t in self.time]
            new_gr.time += time
            new_gr.duration += self.duration
        
        new_gr.sound = self.sound
        return new_gr
    
    def __mod__(self, obj):
        new_gr = Groove()
        new_gr.gates = self.gates.copy()
        new_gr.duration = self.duration.copy()
        new_gr.time = self.time.copy()
        if isinstance(obj, Sound):
            new_gr.sound = {'x': obj}
        elif isinstance(obj, dict):
            new_gr.sound = obj
        # print([str(gate) for gate in new_gr.gates])
        return new_gr
 
    
class Piece:
    def __init__(self, backend='reaper',
                 bpm=120,
                 track_length=4,
                 sample_rate=44100,
                 buffer_size=256,
                 cwd='.',
                 apply_immediately=True
                 ):
        self.bpm = bpm
        self.track_length = track_length
        self.sample_rate = sample_rate
        self.buffer_size = buffer_size
        self.cwd = cwd
        self.apply_immediately = apply_immediately

        self.backend_name = backend
        self.reaper_remote = None
        self.dawdmr_engine = None
        self.dawdmr_graph = []
        
        if backend == 'reaper':
            self.reaper_remote = remote.ReaperRemote()
        elif backend == 'dawdreamer':
            self.dawdmr_engine = dawdmr.RenderEngine(sample_rate=sample_rate, block_size=buffer_size)
        
        self.graph = []
    
    def update(self):
        if self.apply_immediately:
            if self.reaper_remote is not None:
                self.reaper_remote.wake()
                
            elif self.dawdmr_engine is not None:
                self.dawdmr_graph = []
                instrs = {}
                for track in self.graph:
                    for grv in track:
                        for sym in grv.sound:
                            for gate, duration, time in zip(grv.gates, grv.duration, grv.time):
                                # print(gate.sym, sym)
                                if gate.sym == sym:
                                    print(gate, duration, time)
                                    if gate.sym not in instrs:
                                        if grv.sound[sym].sample is None:
                                            instr = self.dawdmr_engine.make_plugin_processor(sym, grv.sound[sym].path)
                                        else:
                                            instr = self.dawdmr_engine.make_sampler_processor(sym, load_audio_file(grv.sound[sym].path)[0])
                                        instrs[gate.sym] = instr
                                    instrs[gate.sym].add_midi_note(gate.degree, gate.vol, time, duration)
                lst = list(instrs.keys())
                for i, sym in enumerate(lst):
                    self.dawdmr_graph.append((instrs[sym], [lst[i - 1]] if i > 0 else []))
                print(self.dawdmr_graph)
                self.dawdmr_engine.load_graph(self.dawdmr_graph)
    
    def play(self, duration, blocking=True):
        self.update()
        
        if self.apply_immediately:
            if self.reaper_remote is not None:
                self.reaper_remote.wake()
                self.reaper_remote.play()
            elif self.dawdmr_engine is not None:
                self.dawdmr_engine.render(seconds=duration)
                audio = self.dawdmr_engine.get_audio().transpose((1, 0))
                sd.play(audio, samplerate=self.sample_rate, blocking=blocking)


g = Gate 
s = Sound
gr = Groove
p = Piece


piece = p('dawdreamer', bpm=140)

kick = s(r"B:\muse_repo\Samples\kick\kick (1).wav")
clap = s(r"B:\muse_repo\Samples\clap\clap (3).wav")

piece.graph = [
    [gr('x--xx-x-') * 4 % {'x': kick} +
     gr('xxxxxxxx', 1/8) * 2 % {'x': kick} +
     gr('xxxxxxxx', 1/16) * 1 % {'x': kick}, ],

    [gr('x---x---') * 4 % {'x': clap} +
     gr('x---x---') * 4 % {'x': clap}, ],
]

piece.play(10)

